package java8.chapter14.test;

import java.util.function.Function;
import java.util.function.Supplier;

public class PatternMatching {

	static class BinOp extends Expr {
		
		String opname;
		Expr left, right;
		
		public BinOp(String opname, Expr left, Expr right) {
			super();
			this.opname = opname;
			this.left = left;
			this.right = right;
		}

		@Override
		public String toString() {
			return "(" + left + " " + opname + " " + right + ")";
		}
		
	}

	static class Number extends Expr {
		
		int val;

		public Number(int val) {
			super();
			this.val = val;
		}

		@Override
		public String toString() {
			return "" + val;
		}
		
	}

	static class Expr {
	}

	static interface TriFunction<S, T, U, R> {
		R apply(S s, T t, U u);
	}

	public static void main(String[] args) {
		simplify();
		
		Expr e = new BinOp("+", new Number(5), new BinOp("*", new Number(3), new Number(4)));
		Integer result = evaluate(e);
		System.out.println(e + "=" + result);
	}

	private static Integer evaluate(Expr e) {
		Function<Integer, Integer> numcase = val -> val;
		Supplier<Integer> defaultcase = () -> 0;
		TriFunction<String, Expr, Expr, Integer> binopcase = (opname, left, right) -> {
			if ("+".equals(opname)) {
				if (left instanceof Number && right instanceof Number)
					return ((Number) left).val + ((Number) right).val;
				if (right instanceof Number && left instanceof BinOp)
					return ((Number) right).val + evaluate((BinOp) left);
				if (left instanceof Number && right instanceof BinOp)
					return ((Number) left).val + evaluate((BinOp) right);
				if (left instanceof BinOp && right instanceof BinOp)
					return evaluate((BinOp) left) + evaluate((BinOp) right);
			}
			if ("*".equals(opname)) {
				if (left instanceof Number && right instanceof Number)
					return ((Number) left).val * ((Number) right).val;
				if (right instanceof Number && left instanceof BinOp)
					return ((Number) right).val * evaluate((BinOp) left);
				if (left instanceof Number && right instanceof BinOp)
					return ((Number) left).val * evaluate((BinOp) right);
				if (left instanceof BinOp && right instanceof BinOp)
					return evaluate((BinOp) left) * evaluate((BinOp) right);
			}
			return defaultcase.get();
		};
		
		return patternMatchExpr(e, binopcase, numcase, defaultcase);
	}

	private static void simplify() {
		TriFunction<String, Expr, Expr, Expr> binopcase = (opname, left, right) -> {
			if ("+".equals(opname)) {
				if (left instanceof Number && ((Number) left).val == 0)
					return right;
				if (right instanceof Number && ((Number) right).val == 0)
					return left;
			}
			if ("*".equals(opname)) {
				if (left instanceof Number && ((Number) left).val == 1)
					return right;
				if (right instanceof Number && ((Number)right).val == 1)
					return left;
			}
			return new BinOp(opname, left, right);
		};
		Function<Integer, Expr> numcase = val -> new Number(val);
		Supplier<Expr> defaultcase = () -> new Number(0);
		
		Expr e = new BinOp("+", new Number(5), new Number(0));
		Expr match = patternMatchExpr(e, binopcase, numcase, defaultcase);
		if (match instanceof Number)
			System.out.println("Number: " + match);
		else if (match instanceof BinOp)
			System.out.println("BinOp: " + match);
	}

	static <T> T patternMatchExpr(Expr e, TriFunction<String, Expr, Expr, T> binopcase, Function<Integer, T> numcase, 
			Supplier<T> defaultcase) {
		if (e instanceof BinOp)
			return binopcase.apply(((BinOp) e).opname, ((BinOp) e).left, ((BinOp) e).right);
		else if (e instanceof Number)
			return numcase.apply(((Number) e).val);
		else
			return defaultcase.get();
	}

}